package com.hbwxz.thread;

import cn.hutool.core.date.DateUtil;

/**
 * 学习链接：<a href="https://blog.csdn.net/weixin_64916311/article/details/130642898">【Java多线程编程】wait与notify方法详解</a>
 * <p>
 * 演示 wait()方法 ➕notify()方法
 * <p>
 * 演示场景：三个人【tom，lucy，jack】去取钱，此时 ATM机没有钱，然后不使用 wait()方法的情况下会导致可能使线程A一致在 ATM机里边儿进进出出，陷入线程饥饿的状态（线程B 和 线程C一直排不上队）
 * <p>
 * 总结：
 * ① wait、notify、notifyAll 都是 Object 类的方法；
 * ② wait、notify、notifyAll 必须搭配 synchronized 关键字来使用；
 * ③ 不带参数的 wait 方法会造成死锁等、带参数的 wait 方法则不会；
 * ④ wait 方法的初心就是为了线程处于等待、阻塞状态；
 * ⑤ notify 方法的初心就是为了唤醒同一对象调用 wait 方法的随机一个线程；
 * ⑥ notifyAll 方法的初心就是为了唤醒同一对象调用 wait 方法的所有线程。
 *
 * @author shenzw
 * @date 2023/12/18
 */
public class ThreadWaitAndNotifyDemo {

    private static volatile int money = 0;

    public static void main(String[] args) {

//        waitAndNotify();

        demo01();
    }

    /**
     * 有两线程，main 线程与 thread 线程，main 线程内包含 thread 线程，main 线程内有“Hello main”语句， thread 线程内有“Hello thread”语句。
     * <p>
     * 在 main 线程内创建一个 thread 线程，并且在 thread 线程内使用 Object 类对象调用带参的 wait() 方法，并设置参数 为2000。
     * <p>
     * 在main 方法内使用 Object 类对象调用 notify() 唤醒 thread 线程。使得输出 main 线程内语句后停顿两秒输出 thread 线程内语句。
     */
    private static void demo01() {
        ThreadWaitAndNotifyDemo lockObj = new ThreadWaitAndNotifyDemo();

        new Thread(() -> {
            synchronized (lockObj) {
                try {
                    lockObj.wait(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(DateUtil.now() + " Hello thread");
        }).start();

        synchronized (lockObj) {
            lockObj.notify();
        }
        System.out.println(DateUtil.now() + " Hello main");
    }

    private static void waitAndNotify() {
        ThreadWaitAndNotifyDemo lockObj = new ThreadWaitAndNotifyDemo();

        Thread tom = new Thread(() -> {
            synchronized (lockObj) {
                System.out.println(Thread.currentThread().getName() + "取钱中...");
                getMoney(lockObj);
            }
        }, "tom");
        tom.start();

        Thread lucy = new Thread(() -> {
            synchronized (lockObj) {
                money = 100;
                System.out.println(Thread.currentThread().getName() + "取钱中...");
                getMoney(lockObj);
            }
        }, "lucy");
        lucy.start();

        Thread jack = new Thread(() -> {
            synchronized (lockObj) {
                System.out.println(Thread.currentThread().getName() + "取钱中...");
                getMoney(lockObj);
            }
        }, "jack");
        jack.start();
    }

    /**
     * 模拟取钱
     */
    private static void getMoney(ThreadWaitAndNotifyDemo lockObj) {
        if (money == 0) {
            try {
                System.out.println(String.format("您好，%1s，ATM机暂时没有钱哦，请稍后再来.", Thread.currentThread().getName()));
                lockObj.wait();
                if (money == 0) {
                    System.out.println(String.format("您好，%1s，ATM机暂时没有钱哦，请稍后再来.", Thread.currentThread().getName()));
                    lockObj.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        lockObj.notify();
        System.out.println(String.format("%s，取了%d元.", Thread.currentThread().getName(), money));
        money = money - 50;
    }
}
